Skip to content

Keep keyboard focus off hidden browser tabs - #4310

Merged
SawyerHood merged 1 commit into
get-bb:mainfrom
hemaaanth:fix/hidden-browser-tab-focus
Sep 24, 2026
Merged

SawyerHood merged 1 commit into
get-bb:mainfrom
hemaaanth:fix/hidden-browser-tab-focus

Conversation

@hemaaanth

Copy link
Copy Markdown
Contributor

Human comments

What was wrong

Electron gives a WebContentsView native keyboard focus when its page navigates, even while the view is hidden with setVisible(false). A persisted desktop Browser tab whose page reloads on its own (a retry timer, a meta refresh, live reload) therefore took focus from the BB window every time it navigated. The operator lost the composer in every other thread, and on the New thread page. The view manager forwarded that focus to the renderer as a normal browser focus. #4303 added a guard that returns focus to the host, but only for automation-controlled tabs. See #4308 for the investigation, the live trace and a standalone Electron repro.

What changed

  • apps/desktop/src/desktop-browser-view.ts:
    • A hidden entry (!entry.visible) now takes the same path as an automation-controlled one: its native focus is not reported, and returnFocusToHost gives focus back to the host on the next tick.
    • controlledViewHasFocus treats hidden entries the same way, so that return actually runs.
  • Visible tabs are unchanged. So is explicit focus (focusEntryWithoutNotifying, Take over), which still sets suppressNextFocusNotification first.
  • apps/desktop/test/desktop-browser-view-manager.test.ts: one regression test. A hidden tab's native focus returns to the host and does not push bb-desktop:browser:focused.
  • No wire, HOST_DAEMON_PROTOCOL_VERSION, CLI, SDK or documentation changes.

How you verified

  • The new test fails on main (focusHostWebContents is never called) and passes with the fix.
  • pnpm exec turbo run test --filter=@bb/desktop --force: 47 files, 448 tests passed, 1 skipped. One earlier run hit a transient failure in electron-builder-config.test.ts, which is unrelated to this change. That file passes on its own both with and without this change, and on the rerun.
  • pnpm exec turbo run typecheck lint --filter=@bb/desktop passed. oxfmt --check passes on the changed source file. The only formatter findings in the test file are in pre-existing code outside this diff, and I left them untouched.
  • Real Electron 44.3.0 under Xvfb, driving the real createDesktopBrowserViewManager, using the repro script in Hidden desktop Browser tab steals keyboard focus whenever its page navigates on its own #4308. The script attaches a tab, hides it, focuses the host, and lets the page reload itself.
    • main at 6f9d6f44d reproduced in 3 of 3 runs. Each reload blurred the host, pushed bb-desktop:browser:focused, and left host.isFocused=false while win.isFocused=true.
    • With this change, 3 of 3 runs showed each reload's host blur followed by host focus within about 1 ms, no focus push, and host.isFocused=true / document.hasFocus=true at every poll.

Fixes #4308

AGENT GENERATED

A hidden desktop Browser tab takes native keyboard focus whenever its page
navigates on its own (a reload timer, meta refresh, or live reload), so typing
in any other thread loses the composer. get-bb#4303 returns that focus to the host
for automation-controlled tabs; apply the same guard to hidden tabs.

Fixes get-bb#4308

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@SawyerHood
SawyerHood merged commit 2f61160 into get-bb:main Sep 24, 2026
16 checks passed
ymichael added a commit that referenced this pull request Nov 3, 2026
…6917)

## Human comments

## What was wrong

When a page navigates on its own, Electron gives its `WebContentsView`
native keyboard focus. #4310 returned that focus to the host only for
hidden or agent-controlled tabs. A visible Browser tab showing a
self-refreshing page, such as the connect-next "Nothing answered at this
address" page (meta refresh every 10 s,
`apps/connect-next/src/pages.ts`), still pulled the cursor out of the
composer on every reload.

## What changed

`apps/desktop/src/desktop-browser-view.ts`:

- On `did-start-loading`, a tab that does not have focus is marked as
waiting for a pointer press (`pointerFocusListener`, an `input-event`
listener).
- `rejectsNativeFocus` now also covers marked tabs. Focus a marked tab
gains is returned to the host one tick later, through the same path as
hidden tabs.
- The mark clears on a pointer press inside the page (`mouseDown`,
`pointerDown`, `touchStart`, `gestureTapDown`) or when bb focuses the
tab explicitly (`focusEntryWithoutNotifying`). Explicit focus covers
address bar submit, Take over and the pane focus shortcuts. Mouse moves
and scrolls do not clear it.
- The focus decision waits one tick, so a click counts whether Electron
delivers `focus` before or after the `mouseDown`. The `focused` push is
sent only if the tab still has focus at that point.

There are no wire, CLI or docs changes.

## How you verified

- New tests in `apps/desktop/test/desktop-browser-view-manager.test.ts`:
  - A reloading visible tab returns focus to the host on repeated loads.
- A reloaded tab keeps focus after a press that lands before the focus,
a press that follows it, or an explicit focus.
- `pnpm exec turbo run test --filter=@bb/desktop --
test/desktop-browser-view-manager.test.ts`: 114 passed.
- `pnpm exec turbo run typecheck --filter=@bb/desktop`: clean.
- Manual check in a packaged `pnpm desktop:worktree` build against a
local page that reloads every 3 s is in progress. Still to confirm: the
composer keeps focus across reloads; clicking into the page keeps focus;
address bar, Take over and pane shortcuts still focus the page.

> AGENT GENERATED

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hidden desktop Browser tab steals keyboard focus whenever its page navigates on its own

2 participants